package com.allwees.core.elasticsearch.query.sql;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.allwees.core.common.util.json.JsonUtil;
import com.allwees.core.common.util.reflect.ReflectionUtils;
import com.allwees.core.elasticsearch.configuration.ElasticsearchScriptTemplate;
import com.allwees.core.elasticsearch.http.Method;
import com.allwees.core.elasticsearch.query.EsQueryException;
import com.allwees.core.elasticsearch.query.QueryParams;
import com.allwees.core.elasticsearch.query.ScriptRequest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 通用Elasticsearch Sql查询
 *
 * @author yangylsky
 * @since 2020-04-08
 *
 */
@Slf4j
@Repository
public class EsSqlQuery {
	@Autowired
	private ElasticsearchScriptTemplate esScriptTemplate;

	private static final Map<String, String> CACHE_QUERY = new HashMap<>();

	public void clearQueryCache() {
		CACHE_QUERY.clear();
	}

	public String query(String fileUri) {
		return query(fileUri, QueryParams.of());
	}

	public String query(String fileUri, QueryParams params) {
		String queryJson = _getQueryJson(fileUri);

		ScriptRequest request = new ScriptRequest()
				.setMethod(Method.POST)
				.setSuffix("_sql?format=json")
				.setScript(queryJson)
				.setParams(params);

		return esScriptTemplate.performRequest(request);
	}

	public  long updateSingleFieldByCondition(String indexName,String conditionFieldName, String conditionValue,String targetFieldName,String targetValue) {
		return esScriptTemplate.updateSingleFieldByCondition(indexName,conditionFieldName,conditionValue,targetFieldName,targetValue);
	}

	public JSONObject queryJsonObj(String fileUri, QueryParams params) {
    	String json = query(fileUri, params);
		return JsonUtil.toJSONObject(json);
	}

	private static String _getQueryJson(String fileUri) {
		String queryJson = CACHE_QUERY.get(fileUri);
		if(queryJson == null) {
			try {
				queryJson = IOUtils.toString(EsSqlQuery.class.getResourceAsStream("/elasticsearch/sqlquery/" + fileUri), StandardCharsets.UTF_8);
			} catch(IOException e) {
				log.error("", e);
				throw new EsQueryException("", e);
			}
		}
		return queryJson;
	}

	public <T> T queryOne(Class<T> clazz, String fileUri, QueryParams params) {
		List<T> results = query(clazz, fileUri, params);
		try {
			return CollectionUtils.isEmpty(results) ? clazz.newInstance() : results.get(0);
		} catch(InstantiationException | IllegalAccessException e) {
			return null;
		}
	}

	public <T> List<T> query(Class<T> clazz, String fileUri, QueryParams params) {
		JSONObject jsonObj = queryJsonObj(fileUri, params);
		JSONArray valueArr = (JSONArray) jsonObj.get("rows");
		if(CollectionUtils.isEmpty(valueArr)){
			return new ArrayList<>(0);
		}
		EsColumn[] columns = _parseColumns(jsonObj);
		List<T> results = new ArrayList<>();
		valueArr.forEach(item -> {
			JSONArray objs = (JSONArray) item;
			T result = ReflectionUtils.createInstance(clazz);
			results.add(result);
			for(int i = 0; i < columns.length; i++) {
				Class<?> valueType = ReflectionUtils.getField(result, columns[i].name).getType();
				Object value = objs.getObject(i, valueType);
				ReflectionUtils.setFieldValue(result, columns[i].name, value);
			}
		});
		return results;
	}

	private EsColumn[] _parseColumns(JSONObject jsonObject) {
		JSONArray columnArr = (JSONArray) jsonObject.get("columns");
		if(columnArr == null){
			return new EsColumn[0];
		}
		EsColumn[] columns = new EsColumn[columnArr.size()];
		for(int i = 0; i < columnArr.size(); i++) {
			JSONObject columnInfo = (JSONObject) columnArr.get(i);
			columns[i] = new EsColumn(columnInfo.getString("name"), columnInfo.getString("type"));
		}
		return columns;
	}

	@Data
	@AllArgsConstructor
	private static class EsColumn {
		private String name;

		private String type;
	}
}
